﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Littlefish.Data;

namespace Littlefish.Model.Objects
{
    public class Student : BaseBusinessObject<Data.Student, Data.MainDataContext>
    {
        #region - Constructors -
        internal Student() : base() { }
        internal Student(MainDataContext context) : base(context) { }
        #endregion

        #region - Public Properties -
        private Demographic m_demographic;
        public Demographic Demographic
        {
            get
            {
                if (m_demographic == null)
                {
                    if (Entity.Demographic == null)
                    {
                        Data.Demographic dem = new Littlefish.Data.Demographic();
                        this.Entity.Demographic = dem;
                    }
                    m_demographic = new Demographic(Context, this.Entity.Demographic);
                }
                return m_demographic;
            }
        }
        #endregion

        #region - Overriden Methods -
        protected override void CheckValidationRules(Littlefish.Data.Student entity)
        {
            if (string.IsNullOrEmpty(entity.RefId) || entity.RefId.Length != 32)
                entity.RefId = App.GenerateRefId();

            this.ValidationErrors.Assert(string.IsNullOrEmpty(entity.LastName), "Cannot be blank", "LastName");

            this.ValidationErrors.Assert(string.IsNullOrEmpty(entity.FirstName), "Cannot be blank", "FirstName");

            if (!Demographic.Validate())
            {
                this.ValidationErrors.AddRange(Demographic.ValidationErrors);
            }
        }
        #endregion

        #region - Public Properties -
        private IEnumerable<Address> m_addresses;
        /// <summary>
        /// Collection of <see cref="Address"/> objects related to the student.
        /// </summary>
        public IEnumerable<Address> Addresses
        {
            get
            {
                if (m_addresses == null)
                {
                    List<Address> addresses = new List<Address>();
                    var addIds = from a in Context.StudentAddresses
                                 where a.StudentId == this.Entity.Id
                                 select a.AddressId;

                    foreach (int id in addIds)
                    {
                        Address a = new Address(this.Context);
                        a.Load(id);
                        addresses.Add(a);
                    }
                    m_addresses = addresses;
                }
                return m_addresses;
            }
        }

        private IEnumerable<EmailAddress> m_emailAddresses;
        /// <summary>
        /// Collection of <see cref="EmailAddress"/> objects related to the student.
        /// </summary>
        public IEnumerable<EmailAddress> EmailAddresses
        {
            get
            {
                if (m_emailAddresses == null)
                {
                    List<EmailAddress> addresses = new List<EmailAddress>();
                    var emailIds = from a in Context.StudentEmailAddresses
                                   where a.StudentId == this.Entity.Id
                                   select a.EmailAddressId;

                    foreach (int id in emailIds)
                    {
                        EmailAddress a = new EmailAddress(this.Context);
                        a.Load(id);
                        addresses.Add(a);
                    }
                    m_emailAddresses = addresses;
                }
                return m_emailAddresses;
            }
        }
        #endregion

        #region - Public Methods -
        /// <summary>
        /// Attaches an <see cref="Address"/> to the student.
        /// </summary>
        /// <param name="id">The record id of the address to attach to the student.</param>
        /// <returns>True if the address was added succesfully.</returns>
        public bool AddAddress(int id)
        {
            ErrorHandler.SetError();
            //TODO: Refactor to more generic paramter checking utility
            #region - Check Params -
            if (id == 0) //Can't attach address with an invalid PK
            {
                this.ErrorHandler.SetError("Id cannot be 0");
                return false;
            }
            #endregion

            return AddAddress(Address.GetAddress(id, this.Context));
        }
        /// <summary>
        /// Attaches an <see cref="Address"/> to the student.
        /// 
        /// If the <see cref="Address"/> does not share the same context as the student then
        /// the <see cref="Address"/> must have a valid record id.
        /// </summary>
        /// <param name="address">The <see cref="Address"/> to attach to the student.</param>
        /// <returns>True if the <see cref="Address"/> was attached successfully.</returns>
        public bool AddAddress(Address address)
        {
            this.ErrorHandler.SetError();
            //TODO: Refactor to more generic parameter checking utility
            #region - Check Params -
            if (address == null)
            {
                this.ErrorHandler.SetError("Address cannot be null");
                return false;
            }

            //Check to see if the Address is already linked to the student.
            StudentAddress studentAddress = this.Entity.StudentAddresses.FirstOrDefault(a => a.AddressId == address.Entity.Id);
            if (studentAddress != null) //Student is already linked to address.
            {
                this.ErrorHandler.SetError("Student is already linked to the the specified address.");
                return false;
            }
            #endregion

            bool success = false;

            //Contexts are the same. Just add the instance to the StudentAddress collection.
            if (this.Context == address.Context)
            {
                studentAddress = new StudentAddress();
                studentAddress.Address = address.Entity;
                this.Entity.StudentAddresses.Add(studentAddress);
                success = true;
            }
            //Contexts are not the same and address has already been created.
            //Add the address id to the entity and add the entity to the StudentAddress collection.
            else if (address.Entity.LastEdit != null)
            {
                studentAddress.AddressId = address.Entity.Id;
                this.Entity.StudentAddresses.Add(studentAddress);
                success = true;
            }
            else
            {
                //Not sure how to handle not having the same context AND address being a new entity. 
                //Perhaps look into detaching from current context, and attaching to new context. Should
                //not be a common problem.
                ErrorHandler.SetError("Address does not share the same context, and does not contain a valid Id");
                success = false;
            }
            return success;
        }
        /// <summary>
        /// Attaches an <see cref="EmailAddress"/> to the student.
        /// </summary>
        /// <param name="id">The record id of the address to attach to the student.</param>
        /// <returns>True if the address was added succesfully.</returns>
        public bool AddEmailAddress(int id)
        {
            ErrorHandler.SetError();
            //TODO: Refactor to more generic paramter checking utility
            #region - Check Params -
            if (id == 0) //Can't attach email address with an invalid PK
            {
                this.ErrorHandler.SetError("Id cannot be 0");
                return false;
            }
            #endregion

            return AddEmailAddress(EmailAddress.GetEmailAddress(id, this.Context));
        }
        /// <summary>
        /// Attaches an <see cref="EmailAddress"/> to the student.
        /// 
        /// If the <see cref="EmailAdress"/> does not share the same context as the student then
        /// the <see cref="EmailAddress"/> must have a valid record id.
        /// </summary>
        /// <param name="EmailAddress">The <see cref="EmailAddress"/> to attach to the student.</param>
        /// <returns></returns>
        public bool AddEmailAddress(EmailAddress emailAddress)
        {
            this.ErrorHandler.SetError();
            //TODO: Refactor to more generic parameter checking utility
            #region - Check Params -
            if (emailAddress == null)
            {
                this.ErrorHandler.SetError("Email address cannot be null");
                return false;
            }

            //Check to see if the Email address is already linked to the student.
            StudentEmailAddress studentEmailAddress = this.Entity.StudentEmailAddresses.FirstOrDefault(a => a.EmailAddressId == emailAddress.Entity.Id);
            if (studentEmailAddress != null) //Student is already linked to email address.
            {
                this.ErrorHandler.SetError("Student is already linked to the the specified email address.");
                return false;
            }
            #endregion

            bool success = false;

            //Contexts are the same. Just add the instance to the StudentEmailAddress collection.
            if (this.Context == emailAddress.Context)
            {
                studentEmailAddress = new StudentEmailAddress();
                studentEmailAddress.EmailAddress = emailAddress.Entity;
                this.Entity.StudentEmailAddresses.Add(studentEmailAddress);
                success = true;
            }
            //Contexts are not the same and email address has already been created.
            //Add the address id to the entity and add the entity to the StudentEmailAddress collection.
            else if (emailAddress.Entity.LastEdit != null)
            {
                studentEmailAddress.EmailAddressId= emailAddress.Entity.Id;
                this.Entity.StudentEmailAddresses.Add(studentEmailAddress);
                success = true;
            }
            else
            {
                //Not sure how to handle not having the same context AND address being a new entity. 
                //Perhaps look into detaching from current context, and attaching to new context. Should
                //not be a common problem.
                ErrorHandler.SetError("Address does not share the same context, and does not contain a valid Id");
                success = false;
            }
            return success;
        }
        #endregion

        #region - Static Methods -
        public static Student CreateStudent()
        {
            return CreateStudent(null, null);
        }
        public static Student CreateStudent(BusinessObjectOptions options)
        {
            return CreateStudent(null, options);
        }
        public static Student CreateStudent(MainDataContext context)
        {
            return CreateStudent(context, null);
        }
        public static Student CreateStudent(MainDataContext context, BusinessObjectOptions options)
        {
            Student s = new Student(context);
            if (options != null)
                s.Options = options;

            s.NewEntity();
            return s;
        }

        public static Student GetStudent(int id)
        {
            return GetStudent(id, null, null);
        }
        public static Student GetStudent(int id, BusinessObjectOptions options)
        {
            return GetStudent(id, null, options);
        }
        public static Student GetStudent(int id, MainDataContext context)
        {
            return GetStudent(id, context, null);
        }
        public static Student GetStudent(int id, MainDataContext context, BusinessObjectOptions options)
        {
            Student student = new Student(context);

            if (options != null)
                student.Options = options;

            student.Load(id);
            return student;
        }

        public static bool AddAddressToStudent(int studentId, int addressId)
        {
            bool success = false;
            if (studentId != 0 && addressId != 0)
            {
                Student s = GetStudent(studentId);
                if (s.Entity.Id == studentId)
                    success = s.AddAddress(addressId);
            }
            return success;
        }
        public static IEnumerable<Student> GetStudents()
        {
            MainDataContext context = new MainDataContext();
            List<Student> students = new List<Student>();
            var entities = from s in context.Students
                           orderby s.Id
                           select s;
            foreach (Data.Student s in entities)
            {
                Student student = new Student(context);
                student.Entity = s;
                students.Add(student);
            }
            return students;
        }
        #endregion
    }
}
